using System;
using System.Collections.Generic;

namespace Framework
{
    public class FSM
    {
        #region Internal Class
        private class State
        {
            public string Name;
            public Action OnEnter;
            public Action OnExecute;
            public Action OnExit;

            public State(string name, Action onEnter, Action onExecute, Action onExit)
            {
                Name = name;
                OnEnter = onEnter;
                OnExecute = onExecute;
                OnExit = onExit;
            }
        }
        #endregion
        
        private State currState = null;
        public string CurrentState => currState?.Name;
        private State defaultState = null;
        private readonly Dictionary<string, State> states = new();
        private readonly Dictionary<(string from, string to), Func<bool>> transitions=new();
        public bool AllowUnregisteredTransitions = true;
        
        
        
        public event Action<(string from, string to)> OnTransitionEvent;
        
        public void AddState(string name, Action onEnter=null, Action onExecute=null, Action onExit=null) 
        {
            if (name == null) throw new ArgumentNullException(nameof(name));
            if (states.ContainsKey(name)) throw new ArgumentException($"State with name {name} already exists");
            states.Add(name, new State(name, onEnter, onExecute, onExit));
        }

        public void SetDefaultState(string name)
        {
            defaultState = states.GetValueOrDefault(name, null);
        }

        public void RemoveState(string name)
        {
            if (name == null) throw new ArgumentNullException(nameof(name));
            if (states.Remove(name))
            {
                HashSet<(string, string)> toRemove = new();
                foreach (var (from, to) in transitions.Keys)
                {
                    if (from == name || to == name) toRemove.Add((from, to));
                }
                foreach (var k in toRemove)
                {
                    transitions.Remove(k);
                }
            }
            if (defaultState?.Name == name) defaultState = null;
            if (currState?.Name == name) ChangeState(null);
        }

        public void AddTransition(string from, string to, Func<bool> condition = null)
        {
            if (transitions.ContainsKey((from, to))) transitions[(from, to)] = condition;
            else transitions.Add((from, to), condition);
        }

        public void RemoveTransition(string from, string to)
        {
            transitions.Remove((from, to));
        }

        public bool AllowTransition(string to)
        {
            if (currState == null || to == null) return true;
            if (transitions.TryGetValue((currState.Name, to), out var condition))
                return condition?.Invoke() ?? true;
            return AllowUnregisteredTransitions;
        }

        private bool isChangingState = false;
        public void ChangeState(string target)
        {
            if (isChangingState) throw new InvalidOperationException("Transitions are not allowed simultaneously.");
            isChangingState = true;
            if (AllowTransition(target))
            {
                var nextState = states.GetValueOrDefault(target, defaultState);
                if (currState?.Name == nextState?.Name) return;
                currState?.OnExit?.Invoke();
                nextState?.OnEnter?.Invoke();
                OnTransitionEvent?.Invoke((currState?.Name, nextState?.Name));
                currState = nextState;
            }
            isChangingState = false;
        }
        
        public void OnExecute() => currState?.OnExecute?.Invoke();
    }
}